package com.wgo.bpot.server.service;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

import com.wgo.bpot.common.transport.exception.RematoException;
import com.wgo.bpot.common.transport.exception.db.DatabaseException;
import com.wgo.bpot.common.transport.servicefacade.DomainServerService;
import com.wgo.bpot.remote.DomainRemote;
import com.wgo.bpot.remote.RemotableDomain;
import com.wgo.bpot.server.persist.PersistService;

public class DomainServerServiceImpl implements DomainServerService {
	private static final transient Logger log = Logger.getLogger(DomainServerServiceImpl.class);
	private static final transient String PERSIST_SERVICE_IMPL = "com.wgo.bpot.server.persist.hibernate.HibernatePersistService";
	
	private static PersistService persistService;
	
	static {
		try {
			Class<PersistService> hibernatePersistService = (Class<PersistService>) Class.forName(PERSIST_SERVICE_IMPL);
			persistService = hibernatePersistService.newInstance();
		} catch (Exception e) {
			log.fatal("Unable to locate persistService implementation " + PERSIST_SERVICE_IMPL);
			e.printStackTrace();
		}
	}
	
	public Object executeRemote(RemotableDomain remotableDomain,
			String methodName, Object[] args) {
		Object persistent = remotableDomain;
		Object conceptEjb = loadEjb(persistent);
		Class[] parameterTypes = null;
		if (null != args) {
			parameterTypes = new Class[args.length];
			for (int i = 0; i < args.length; i++) {
				parameterTypes[i] = args[i].getClass();
			}
		}
		Object result = null;
		DomainRemote domainRemote = null;
		Method methodToInvoke = null;
		try {
			Class<DomainRemote> domainRemoteClass = (Class<DomainRemote>) remotableDomain.getClass().getMethod("serverCall").getReturnType();
			domainRemote = createInstance(domainRemoteClass);
			domainRemote.setConcept(conceptEjb);
			methodToInvoke = domainRemote.getClass().getMethod(methodName, parameterTypes);
			log.info("Invoking DomainServerServiceImpl method: " + methodToInvoke.toGenericString());
			
			Object[] convertedArgs = args;
			if ((null != args) && (0 < args.length)) {
				convertedArgs = new Object[args.length];
				for (int i = 0; i < args.length; i++) {
					Object arg = args[i];
					convertedArgs[i] = convertToEjb(arg);
				}
			}
			result = methodToInvoke.invoke(domainRemote, convertedArgs);
		} catch (Exception e) {
			String mName = "UNKNOWN";
			if (null != methodToInvoke) {
				mName = methodToInvoke.toGenericString();
			}
			e.printStackTrace();
			throw new RematoException("Unable to invoke remote method " + mName,e);
		}
		result = convertToDomainModel(result);
		return result;
	}

	private Object convertToEjb(Object toBeConverted) {
		Object result = toBeConverted;
		if (null != toBeConverted) {
			if (Collection.class.isInstance(toBeConverted)) {
				if (Set.class.isInstance(toBeConverted)) {
					result = new HashSet();
				} else if (List.class.isInstance(toBeConverted)) {
					result = new ArrayList();
				}
				for (Object element : ((Collection)toBeConverted)) {
					((Collection)result).add(convertToEjb(element));
				}
			} else if (persistService.getDomainModelConvention().getDomainModelInterface().isInstance(toBeConverted)) {
				result=loadEjb(toBeConverted);
			}
		}
		return result;
	}

	private Object convertToDomainModel(Object toBeConverted) {
		Object result = toBeConverted;
		if (null == toBeConverted) {
			return result;
		}
		if (Collection.class.isInstance(toBeConverted)) {
			if (Set.class.isInstance(toBeConverted)) {
				result = new HashSet();
			} else if (List.class.isInstance(toBeConverted)) {
				result = new ArrayList();
			}
			for (Object element : ((Collection)toBeConverted)) {
				((Collection)result).add(convertToDomainModel(element));
			}
		} else if (persistService.getDomainModelConvention().getDomainModelInterface().isInstance(toBeConverted)) {
//FIXME			result = ServerModelConverter.convertToDomainModel((Persistent)toBeConverted, 1, new Hashtable<String,Object>(),null);
		}
		return result;
	}

	private DomainRemote createInstance(Class<DomainRemote> domainRemoteClass) {
		String packageName = domainRemoteClass.getPackage().getName() + ".server.";
		String className = packageName + domainRemoteClass.getSimpleName() + "Impl";
		DomainRemote serverInstance = null;
		try {
			Class<DomainRemote> serverClass = (Class<DomainRemote>) this.getClass().getClassLoader().loadClass(className);
			serverInstance = serverClass.newInstance();
		} catch (Exception e) {
			throw new RematoException("Unable to create server instance " + className,e);
		}
		return serverInstance;
	}

	
	@SuppressWarnings("unchecked")
	private static Object loadEjb (Object domain) {
//FIXME		Class<Persistent> ejbImplClass = ServerModelConverter.loadServerClass(domain.getClass().getInterfaces()[0]);
//FIXME		return load(ejbImplClass, domain.getDbId(), persistService);
		return null;
	}
	private static Object load (Class clazz, Long dbId, PersistService persistService) {
		Object ejbInstance = persistService.getDbInstance(clazz, dbId); //TODO??
		if (null == ejbInstance) {
			throw new DatabaseException("Could not load ejb instance from database " + clazz.getInterfaces()[0]);
		}
		return ejbInstance;
	}
	
}
